---
title: สร้างรายได้จากโลกของคุณ! การตั้งค่าผลิตภัณฑ์และสกุลเงิน
slug: world-sdk-guide-th/AJYW4QT1rlGT8cByRsHkg
docTags: 
createdAt: 2024-09-20T13:21:55.288Z
---

ZEPETO Mannequin คือแพ็กเกจ API หุ่นที่รองรับฟังก์ชันลองสวมใส่ / ซื้อคอสตูม

- ทำให้สามารถขายไอเทมคอสตูมที่ตัวเองสร้างและเผยแพร่ใน World ได้
- สามารถสร้างให้หุ่นสวมคอสตูมที่กำหนดบน NPC ตัวละคร ZEPETO ได้
- สามารถซื้อไอเทมคอสตูมได้จากการมีปฏิกิริยากับหุ่นหรือวัตถุ

::embed[]{url="https://www.youtube.com/embed/sag4H7B5oM4"}

## STEP 1 : ติดตั้ง

Window → Package Manager → และติดตั้ง ZEPETO.World Package 1.21.15 หรือสูงกว่าก่อน

หลังจากนั้นกรุณาติดตั้งแพ็คเกจ ZEPETO.Module 1.0.8

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/udZX8YuF8hT6tVw-JlCU4_image.png)

:::hint{type="danger"}
❗️ **คำเตือน**

- ในส่วนของการปรับปรุงประสิทธิภาพ แพ็คเกจแบบสแตนด์อโลนได้ถูกรวมเข้ากับแพ็คเกจ ZEPETO.Module 1.0.8 และเวอร์ชันที่ใหม่กว่า
  - โปรดลบแพ็คเกจ ZEPETO.Mannequin ที่ติดตั้งไว้ก่อนหน้านี้
  - หลังจากลบแพ็คเกจที่มีอยู่แล้ว โปรดติดตั้งแพ็คเกจ ZEPETO.Module เวอร์ชัน 1.0.8 หรือใหม่กว่า
:::


STEP 2 : ตรวจสอบ ID รายการที่จะตั้งค่าบนหุ่นจำลอง
-------------------------------------------------

:::hint{type="success"}
👍 **วิธีตรวจสอบ ID ไอเทม**

- คุณสามารถตรวจสอบ URL ที่ปรากฏขึ้นเมื่อคุณคลิกที่สินค้ารายการใน ZEPETO Studio

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/yf-uLckQ1oUjFdhFUvKJa_image.png)

- คัดลอก ID รายการในรูปแบบของตัวเลขสุ่ม วางลงใน Unity Editor และวาง 'CR\_' ข้างหน้า

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/m9_sRwGW49zqKSVmGoCWm_image.png)
:::

:::hint{type="info"}
📘 หากเพิ่งสร้างไอเทมเป็นครั้งแรก กรุณาศึกษาจากแนวทางต่อไปนี้ \[[Create Your Item](https://docs.zepeto.me/studio/lang-id/docs/welcome)]
:::

## STEP 3 : เซ็ตหุ่น

สามารถสร้างวัตถุหุ่นได้ทั้งหมด 3 วิธี

:::hint{type="danger"}
**❗️ คำเตือน**

- แพ็กเกจ ZEPETO Mannequin สามารถใช้สำหรับเซ็ตและขายได้แค่ไอเทมที่ตัวเองสร้างและเผยแพร่ใน World เท่านั้น
- ไม่สามารถขายไอเทมที่ถูกตีกลับในขั้นตอนตรวจสอบได้
- เพราะฉะนั้น ต้องล็อกอิน Unity เท่านั้น จึงจะใช้ฟังก์ชันนี้ได้
- ตอนที่เผยแพร่แพ็กเกจที่มีฟังก์ชันหุ่น ก็ต้องล็อกอิน Editor ไว้เช่นกัน
- เมื่อใช้หุ่นของเทมเพลต ZEPETO หรือประเภทโมเดล ZEPETO จำนวนหุ่นอาจใช้ทรัพยากรมากเท่ากับจำนวนอักขระ ZEPETO ที่ป้อน ซึ่งอาจส่งผลต่อการปรับให้เหมาะสม
  - หากคุณต้องการวางหุ่นจำนวนมากให้เป็นรูปร่างของตัวละคร Zepeto ในโลก เราขอแนะนำให้เลือกหุ่นแบบ Simple Type และใช้ [ZEPETO BaseModel](https://studio.zepeto.me/console/download/652892d3a80a9f5505ed888f) เป็นวัตถุ
:::

**STEP 3-1 : Simple Type**

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/AnIz9yZZwU8C4YF0ndlWk_image.png)

เป็นวิธีที่ง่ายที่สุด ซึ่งสามารถเพิ่มวัตถุที่กำหนดให้เป็น Mannequin ลูก เพื่อเซ็ตลักษณะภายนอกได้

1. เพิ่มวัตถุที่จะโต้ตอบด้วย
2. ต้องเพิ่ม Collider ลงในวัตถุเพื่อให้มีปฏิกิริยา

- ต้องเช็ก isTrigger หากไม่ได้เช็ก จะถูกประเมินเป็นทริกเกอร์อัตโนมัติ
- หากจะเพิ่ม Component ทั้งที่ไม่ได้เพิ่ม Collider จะมีหน้าต่างแจ้งเตือนปรากฏขึ้นดังด้านล่าง

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/fK_5Ha1-AuFTtChPiw30n_image.png)

1. เพิ่ม Conponent หุ่นลงในวัตถุ
2. ในตัวตรวจสอบให้ตั้งค่าดังนี้:
   - icon: จะแสดงไอคอนไม้แขวนเสื้อเป็นค่าเริ่มต้น แต่สามารถเปลี่ยนเป็นไอคอนคัสตอมได้
   - icon position: หากไม่มี ไอคอนจะปรากฏบนตำแหน่งวัตถุที่มี Component หุ่นเป็นค่าเริ่มต้น
     - เพิ่ม position object จากนั้นป้อนค่าลงไปเพื่อให้ปรากฏในตำแหน่งที่ต้องการ
   - ids: กรอก ID ไอเทมคอสตูมที่ต้องการขาย

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/rlyVQG8Ki9R23_qRnXFa2_image.png)

1. เพิ่ม Empty Object แล้วตั้งชื่อวัตถุว่า MannequinScript
2. ZEPETO > เพิ่ม Typescript แล้วตั้งชื่อสคริปต์ว่า MannequinScript
3. เขียนสคริปต์ดังด้านล่าง

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { ZepetoPlayers, SpawnInfo } from 'ZEPETO.Character.Controller';
import { WorldService } from 'ZEPETO.World';
import { ItemContentsRequest, Mannequin, MannequinComponent, MannequinInteractable, MannequinPreviewer } from 'ZEPETO.Mannequin';
import { Object } from 'UnityEngine';

export default class MannequinScript extends ZepetoScriptBehaviour {

    private _previewer: MannequinPreviewer;

    Start() {
        // Code that creates a ZEPETO character based on the logged in ID  
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
      
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // Add Mannequin Interactable Component
            character.gameObject.AddComponent<MannequinInteractable>();
        });

        // Find all Mannequin components
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();

        mannequins.forEach(m => {
            // Enter the Collider
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });

            // Exit the Collider
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```



1. หลังจากเพิ่มสคริปต์ไปที่วัตถุแล้ว ให้กดปุ่ม \[▶︎(play)] เพื่อเรียกใช้

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/p4Wihq0NAPHpmq67w9MNL_image.png)

### STEP 3-2 : ZEPETO Model Type

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/48aFFWnmATCvwLT3FLf6N_image.png)

สามารถเพิ่ม ID ไอเทมลงใน NPC ตัวละคร ZEPETO เพื่อเซ็ตลักษณะภายนอกของหุ่นได้

1. สร้าง Empty Object แล้วตั้งชื่อว่า Mannequin
2. ต้องเพิ่ม Collider ลงในวัตถุเพื่อให้มีปฏิกิริยา

- ต้องเช็ก isTrigger หากไม่ได้เช็ก จะถูกประเมินเป็นทริกเกอร์อัตโนมัติ

1. เพิ่ม Conponent หุ่นลงในวัตถุ
2. ในตัวตรวจสอบให้ตั้งค่าดังนี้:
   - icon: จะแสดงไอคอนไม้แขวนเสื้อเป็นค่าเริ่มต้น แต่สามารถเปลี่ยนเป็นไอคอนคัสตอมได้
   - icon position: หากไม่มี ไอคอนจะปรากฏบนตำแหน่งวัตถุที่มี Component หุ่นเป็นค่าเริ่มต้น
     - เพิ่ม position object จากนั้นป้อนค่าลงไปเพื่อให้ปรากฏในตำแหน่งที่ต้องการ
   - ids: กรอก ID ไอเทมคอสตูมที่ต้องการขาย
   - pose: สามารถเลือกโพสแล้วเซ็ตได้
     - ในอนาคตจะจัดโพสที่ต้องการให้ในรูปแบบ ID

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/PCaWKswD5g7ACVXzEdwIZ_image.png)

:::hint{type="success"}
👍 หากคุณติดตั้งสคริปต์ Mannequin ในฉากของคุณแล้ว คุณสามารถข้ามขั้นตอนต่อมาได้
:::

1. เพิ่ม Empty Object แล้วตั้งชื่อวัตถุว่า MannequinScript
2. ZEPETO > เพิ่ม Typescript แล้วตั้งชื่อสคริปต์ว่า MannequinScript
3. เขียนสคริปต์ดังด้านล่าง

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { ZepetoPlayers, SpawnInfo } from 'ZEPETO.Character.Controller';
import { WorldService } from 'ZEPETO.World';
import { ItemContentsRequest, Mannequin, MannequinComponent, MannequinInteractable, MannequinPreviewer } from 'ZEPETO.Mannequin';
import { Object } from 'UnityEngine';

export default class MannequinScript extends ZepetoScriptBehaviour {

    private _previewer: MannequinPreviewer;

    Start() {
        // Code that creates a ZEPETO character based on the logged in ID  
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
      
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // Add Mannequin Interactable Component
            character.gameObject.AddComponent<MannequinInteractable>();
        });

        // Find all Mannequin components
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();

        mannequins.forEach(m => {
            // Enter the Collider
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });

            // Exit the Collider
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```

1. หลังจากเพิ่มสคริปต์ไปที่วัตถุแล้ว ให้กดปุ่ม \[▶︎(play)] เพื่อเรียกใช้

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/EMJv5_sf6pElRT4-KznKk_image.png)

### STEP 3-3 : ZEPETO Template Type

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/zok-bPf4RVy7CNBT4hyO__image.png)

หากป้อน ZEPETO ID จะมีหุ่นที่สวมชุดที่คนนั้นสวมใส่อยู่ปรากฏขึ้น แต่หากไม่ใช่ชุดที่สร้างขึ้นเองก็จะไม่ปรากฏ

1. สร้าง Empty Object แล้วตั้งชื่อว่า Mannequin
2. ต้องเพิ่ม Collider ลงในวัตถุเพื่อให้มีปฏิกิริยา

- ต้องเช็ก isTrigger หากไม่ได้เช็ก จะถูกประเมินเป็นทริกเกอร์อัตโนมัติ

1. เพิ่ม Conponent หุ่นลงในวัตถุ
2. ในตัวตรวจสอบให้ตั้งค่าดังนี้:
   - icon: จะแสดงไอคอนไม้แขวนเสื้อเป็นค่าเริ่มต้น แต่สามารถเปลี่ยนเป็นไอคอนคัสตอมได้
   - icon position: หากไม่มี ไอคอนจะปรากฏบนตำแหน่งวัตถุที่มี Component หุ่นเป็นค่าเริ่มต้น
     - เพิ่ม position object จากนั้นป้อนค่าลงไปเพื่อให้ปรากฏในตำแหน่งที่ต้องการ
   - zepeto id: ป้อน ZEPETO ID
   - pose: สามารถเลือกโพสแล้วเซ็ตได้
     - ในอนาคตจะจัดโพสที่ต้องการให้ในรูปแบบ ID

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/ESw381jcbxc0BcB5YHFBd_image.png)

:::hint{type="success"}
👍 หากคุณติดตั้งสคริปต์ Mannequin ในฉากของคุณแล้ว คุณสามารถข้ามขั้นตอนต่อมาได้
:::



1. เพิ่ม Empty Object แล้วตั้งชื่อวัตถุว่า MannequinScript
2. ZEPETO > เพิ่ม Typescript แล้วตั้งชื่อสคริปต์ว่า MannequinScript
3. เขียนสคริปต์ดังด้านล่าง

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { ZepetoPlayers, SpawnInfo } from 'ZEPETO.Character.Controller';
import { WorldService } from 'ZEPETO.World';
import { ItemContentsRequest, Mannequin, MannequinComponent, MannequinInteractable, MannequinPreviewer } from 'ZEPETO.Mannequin';
import { Object } from 'UnityEngine';

export default class MannequinScript extends ZepetoScriptBehaviour {

    private _previewer: MannequinPreviewer;

    Start() {
        // Code that creates a ZEPETO character based on the logged in ID  
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);

        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // Add Mannequin Interactable Component
            character.gameObject.AddComponent<MannequinInteractable>();
        });

        // Find all Mannequin components
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();

        mannequins.forEach(m => {
            // Enter the Collider
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });

            // Exit the Collider
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```

1. หลังจากเพิ่มสคริปต์ไปที่วัตถุแล้ว ให้กดปุ่ม \[▶︎(play)] เพื่อเรียกใช้

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/-8GDERTsjJZ17Qmaj8W0V_image.png)


STEP 4 : ใช้ฟังก์ชันซื้อหุ่น
----------------------------

หากเซ็ตหุ่นได้ถูกต้อง ไอคอนสำหรับมีปฏิกิริยากับหุ่นเมื่อเข้าไปในขอบเขต Collider ของวัตถุหุ่นจะปรากฏขึ้น

หากคลิกที่ไอคอน จะมีหน้าต่างให้ซื้อสินค้าและไอเทมที่เซ็ตไว้ในหุ่นปรากฏขึ้น

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/7xzVgtpM7ESbo_E0Tnw7Z_image.png)

สามารถลองสวมใส่ได้โดยคลิกที่ไอคอน

- ไอเทมที่มีอยู่แล้วจะปรากฏเป็นเครื่องหมายเช็กถูกแทนที่ราคา
  หากออกจากขอบเขต Collider ของวัตถุหุ่น จะถูกยกเลิกการลองสวมใส่และเปลี่ยนกลับไปเป็นชุดเดิม

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/7UQQC_pl5Rv7HVtK8FZuU_image.png)

ต่อไปเป็นรายละเอียดการดำเนินการซื้อตาม Test Scenario

| **Test Environment**                                               | **การดำเนินการซื้อ**                                                                                                                                                                                                                                                                                                                                                                                            |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ซื้อบน Unity Editor                                                | เมื่อซื้อได้สำเร็จระหว่าง Play คอสตูมจะถูกเปลี่ยน แต่หากสิ้นสุดการ Play ก็จะกลับไปเป็นดังเดิม                                                                                                                                                                                                                                                                                                                   |
| ซื้อบน QR Code Mobile Test Environment                             | ในกรณีนี้ เป็นการทดสอบโดยเข้าผ่านเซิร์ฟเวอร์จริง ดังนั้นเมื่อซื้อสำเร็จแล้ว คอสตูมจะถูกเปลี่ยน และแม้จะออกจาก World แล้ว คอสตูมที่เปลี่ยนก็จะยังคงอยู่<br /><br />**นอกจากนี้ หากการซื้อสำเร็จ รายการจะถูกซื้อจากเซิร์ฟเวอร์จริง ดังนั้นอัญมณีที่ถือโดยบัญชีจริงจะถูกหักออกและคุณจะมีรายการเครื่องแต่งกาย เสื้อผ้าที่ซื้อแล้วไม่สามารถทิ้งหรือลบทิ้งได้โดยพลการ ดังนั้นโปรดซื้ออย่างระมัดระวังเมื่อทำการทดสอบ** |
| ซื้อโดยการเข้าผ่านลิงก์ทดสอบหลังจากบันทึกแพ็กเกจลงใน ZEPETO Studio | ในกรณีนี้ เป็นการทดสอบโดยเข้าผ่านเซิร์ฟเวอร์จริง ดังนั้นเมื่อซื้อสำเร็จแล้ว คอสตูมจะถูกเปลี่ยน และแม้จะออกจาก World แล้ว คอสตูมที่เปลี่ยนก็จะยังคงอยู่<br /><br />**นอกจากนี้ หากการซื้อสำเร็จ รายการจะถูกซื้อจากเซิร์ฟเวอร์จริง ดังนั้นอัญมณีที่ถือโดยบัญชีจริงจะถูกหักออกและคุณจะมีรายการเครื่องแต่งกาย เสื้อผ้าที่ซื้อแล้วไม่สามารถทิ้งหรือลบทิ้งได้โดยพลการ ดังนั้นโปรดซื้ออย่างระมัดระวังเมื่อทำการทดสอบ** |

## STEP 5 : ซิงก์ Mannequin Multiplay

Multiplay จะต้องซิงก์กับคอสตูมที่ผู้เล่นอื่นสวมใส่ผ่านการใช้ฟังก์ชันหุ่น

:::hint{type="success"}
👍 ต้องตั้งค่า Multiplay พื้นฐานให้เสร็จเรียบร้อยก่อนสร้างตามรายการด้านล่าง
:::

1. เพิ่มสคริปต์ Client โดยเลือก ZEPETO > Typescript จากนั้นตั้งชื่อสคริปต์ว่า MannequinController
2. เขียนตามด้านล่าง

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { Room } from 'ZEPETO.Multiplay';
import { ClothesPreviewer, ItemContent, ItemContentsRequest, Mannequin, MannequinComponent, MannequinInteractable, MannequinPreviewer } from 'ZEPETO.Mannequin';
import { ZepetoContext } from 'Zepeto';
import { ZepetoPlayer, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { GameObject, Object, Canvas, WaitForSecondsRealtime, LayerMask } from 'UnityEngine';
import { Player } from 'ZEPETO.Multiplay.Schema';
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
 
class CharacterItem {
    property: string;
    id: string;
}
 
class ChangedItem {
    sessionId: string;
    characterItems: CharacterItem[];
}
 
export default class MannequinController extends ZepetoScriptBehaviour {
    private MESSAGE_TYPE = {
        OnChangedItem: "OnChangedItem",
        SyncChangedItem: "SyncChangedItem",
        CheckChangedItem: "CheckChangedItem"
    }
 
    private multiplay: ZepetoWorldMultiplay;
    private _room: Room;
    private _previewer: MannequinPreviewer
    private _context: ZepetoContext
 
    private _userZepetoContexts: Map<string, ZepetoContext> = new Map<string, ZepetoContext>();
    private _currentMannequinComponent: MannequinComponent = null;
    private _selectMannequinComponent: MannequinComponent = null;
    private _isOpenMannequinUI: boolean = false;
 
    private _basicClothString = "basicCloth" as const;
 
    Start() {
        this.multiplay = Object.FindObjectOfType<ZepetoWorldMultiplay>();
 
        Mannequin.OnOpenedShopUI.AddListener((item) => {
            // When you click OpenShopButton
            this.OpenedShopUI(item)
        });
        Mannequin.OnClosedShopUI.AddListener(() => {
            // When you click CloseShopButton
            this.ClosedShopUI()
        });
        Mannequin.OnSelectedItem.AddListener((itemcontent: ItemContent, select: boolean) => {
            // Action when item is selected
        });
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const myPlayer = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer;
 
            // Mannequin
            const character = myPlayer.character;
            character.gameObject.AddComponent<MannequinInteractable>();
 
            console.log("local _context set");
            this._context = character.Context;
 
            const mannequins = Object.FindObjectsOfType<MannequinComponent>()
            mannequins.forEach(mannequin => {
                // When you enter mannequin collider
                mannequin.onActive.AddListener(contents => {
                    if (this._currentMannequinComponent != null && this._currentMannequinComponent == mannequin) {
                        return;
                    }
 
                    if (contents == null || contents.length == 0) {
                        console.log("No mannequin data");
                        return;
                    }
 
                    if (this._isOpenMannequinUI) {
                        this.BreakMannequin();
                    }
 
                    this._selectMannequinComponent = mannequin;
                    Mannequin.OpenUI(contents);
                    console.log("onActive");
                });
 
                // [Option] When you leave mannequin collider
                mannequin.onCancel.AddListener( () => {
                    if (this._currentMannequinComponent == null || this._currentMannequinComponent != mannequin) {
                        return;
                    }
 
                    this.BreakMannequin();
                    console.log("onCancel");
                });
 
                let iconCanvas = mannequin.gameObject.GetComponentInChildren<Canvas>(true);
                if (iconCanvas != null) {
                    iconCanvas.gameObject.layer = LayerMask.NameToLayer("UI");
                }
            });
        });
 
        ZepetoPlayers.instance.OnAddedPlayer.AddListener((sessionId: string) => {
            if (ZepetoPlayers.instance.GetPlayer(sessionId).isLocalPlayer) {
                return;
            }
            const userContext = ZepetoPlayers.instance.GetPlayer(sessionId).character.Context;
            this._userZepetoContexts.set(sessionId, userContext);
            this._room.Send(this.MESSAGE_TYPE.CheckChangedItem, sessionId);
        });
        this.multiplay.RoomJoined += (room: Room) => {
            this._room = room;
            this.AddMessageHandler();
        };
    }
 
    private AddMessageHandler() {
        // [Option] Synchronize each player's clothes
        this._room.AddMessageHandler<ChangedItem>(this.MESSAGE_TYPE.SyncChangedItem, message => {
            console.log("syncChangedItem");
            if (message == null) {
                return;
            }
 
            if (false == this._userZepetoContexts.has(message.sessionId)) {
                return;
            }
 
            let itemContents: ItemContent[] = [];
 
            for (const characterItem of message.characterItems) {
                let itemContent: ItemContent = new ItemContent();
                itemContent.id = characterItem.id;
                itemContent.property = parseInt(characterItem.property);
 
                if (itemContent.id == this._basicClothString) {
                    itemContent.id = "";
                }
 
                itemContents.push(itemContent);
            }
            let clothesPreviewer:ClothesPreviewer = new ClothesPreviewer(this._userZepetoContexts.get(message.sessionId),itemContents);
            clothesPreviewer.PreviewContents();
        });
    }
 
    private ClosedShopUI() {
        this._currentMannequinComponent = null;
        this._isOpenMannequinUI = false;
    }
 
    private OpenedShopUI(items: ItemContent[]) {
        this._isOpenMannequinUI = true;
        this._currentMannequinComponent = this._selectMannequinComponent;
 
        this._previewer = new MannequinPreviewer(this._context, items);
        this._previewer.OnChanged.AddListener((changeValues) => {
            let characterItems: CharacterItem[] = [];
 
            for (const changeValue of changeValues) {
                let characterItem: CharacterItem = new CharacterItem();
                characterItem.id = changeValue.id;
                characterItem.property = changeValue.property.toString();
 
                if (characterItem.id == "") {
                    characterItem.id = this._basicClothString;
                }
                characterItems.push(characterItem);
            }
            this._room.Send(this.MESSAGE_TYPE.OnChangedItem, characterItems);
        });
 
        this._previewer.PreviewContents();
    }
 
    public BreakMannequin() {
        Mannequin.CloseUI();
        if (this._previewer) {
            this._previewer.ResetContents();
            this._previewer = null;
        }
 
        this._currentMannequinComponent = null;
        this._isOpenMannequinUI = false;
    }
 
}
```

1. เลือก Hierarchy > Empty Object เพื่อสร้างวัตถุ แล้วตั้งชื่อว่า MannequinController
2. เพิ่มสคริปต์ที่เพิ่งสร้างลงใน MannequinController

![](https://api.archbee.com/api/optimize/xv-TGeWVTX2GDZMFGOf1Y/msOB-x0FVBpRhk4dRJIiw_image.png)

1. เขียนโค้ดเซิร์ฟเวอร์ใน World.Multiplay > index.ts โดยอ้างอิงตามด้านล่าง

```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from 'ZEPETO.Multiplay';
import { Player, Transform, Vector3 } from 'ZEPETO.Multiplay.Schema';
 
class CharacterItem {
    property: string;
    id: string;
}
 
class ChangedItem {
    sessionId: string;
    characterItems: CharacterItem[];
}
 
enum Cloth {
    TOP = "19",
    BOTTOM = "20" ,
    DRESS = "22"
}
 
export default class extends Sandbox {
    private MESSAGE_TYPE = {
        OnChangedItem: "OnChangedItem",
        SyncChangedItem: "SyncChangedItem",
        CheckChangedItem: "CheckChangedItem"
    }
     
    // Map<sessionId, Map<CharacterItem.property, CharacterItem.id>>
    private ChangedItems: Map<string, Map<string, string>>;
 
    onCreate(options: SandboxOptions) {
        // Called when the Room object is created.
        // Handle the state or data initialization of the Room object.
        this.onMessage("onChangedTransform", (client, message) => {
            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;
 
            player.transform = transform;
        });
        this.onMessage("onChangedState", (client, message) => {
            const player = this.state.players.get(client.sessionId);
            player.state = message.state;
            player.subState = message.subState; // Character Controller V2
        });
 
        // Mannequin server code
        this.ChangedItems = new Map<string, Map<string, string>>();
 
        this.onMessage<CharacterItem[]>(this.MESSAGE_TYPE.OnChangedItem, (client, message) => {
            // Overwrite clothes and set new parts
            if (this.ChangedItems.has(client.userId)) {
                const changedItemMap = this.ChangedItems.get(client.userId);
                for (const characterItem of message) {
                    if (characterItem.property == Cloth.DRESS) {
                        // In the case of a dress (22), remove the top (19) and bottom (20)
                        if (changedItemMap.has(Cloth.TOP)) {
                            changedItemMap.delete(Cloth.TOP);
                        }
                        if (changedItemMap.has(Cloth.BOTTOM)) {
                            changedItemMap.delete(Cloth.BOTTOM);
                        }
                    } else if (characterItem.property == Cloth.TOP || characterItem.property == Cloth.BOTTOM) {
                        // Remove the dress if it is a top (19) or bottom (20)
                        if (changedItemMap.has(Cloth.DRESS)) {
                            changedItemMap.delete(Cloth.DRESS);
                        }
                    }
 
                    changedItemMap.set(characterItem.property,characterItem.id);
                    console.log(`OnChangedItem old ${client.userId} : ${characterItem.property} // ${characterItem.id}`);
                }
            } else {
                // Initial registration
                let changedItemMap: Map<string,string> = new Map<string, string>();
                for (const characterItem of message) {
                    changedItemMap.set(characterItem.property,characterItem.id);
                }
                this.ChangedItems.set(client.sessionId,changedItemMap);
            }
 
            let changedItem: ChangedItem = new ChangedItem();
            changedItem.sessionId = client.sessionId;
            changedItem.characterItems = message;
 
            console.log(`OnChangedItem : ${changedItem.sessionId}`);
            for (const characterItem of changedItem.characterItems) {
                console.log(` ::: ${characterItem.property} - ${characterItem.id} `);
            }
            this.broadcast(this.MESSAGE_TYPE.SyncChangedItem, changedItem, {except: client});
        });
 
        this.onMessage<string>(this.MESSAGE_TYPE.CheckChangedItem,(client, message) => {
            if (false == this.ChangedItems.has(message)) {
                return;
            }
 
            let changedItem: ChangedItem = new ChangedItem();
            changedItem.sessionId = client.sessionId;
            changedItem.characterItems = [];
 
            for (const property of this.ChangedItems.get(message).keys()) {
                let characterItem: CharacterItem = new CharacterItem();
                characterItem.property = property;
                characterItem.id = this.ChangedItems.get(message).get(property);
 
                changedItem.characterItems.push(characterItem);
            }
 
            client.send<ChangedItem>(this.MESSAGE_TYPE.SyncChangedItem, changedItem );
        });
 
    }
 
    onJoin(client: SandboxPlayer) {
 
        // Create the player object defined in schemas.json and set the initial value.
        console.log(`[OnJoin] sessionId : ${client.sessionId}, HashCode : ${client.hashCode}, userId : ${client.userId}`)
 
        const player = new Player();
        player.sessionId = client.sessionId;
      
        if (client.userId) {
            player.zepetoUserId = client.userId;
        }
 
        // Manage the Player object using sessionId, a unique key value of the client object.
        // The client can check the information about the player object added by set by adding the add_OnAdd event to the players object.
        this.state.players.set(client.sessionId, player);
    }
 
    async onLeave(client: SandboxPlayer, consented?: boolean) {
 
        // By setting allowReconnection, it is possible to maintain connection for the circuit, but clean up immediately in the basic guide.
        // The client can check the information about the deleted player object by adding the add_OnRemove event to the players object.
        this.state.players.delete(client.sessionId);
        if (this.ChangedItems.has(client.sessionId)) {
            this.ChangedItems.delete(client.sessionId);
        }
    }
}
```

1. เปิดเซิร์ฟเวอร์ Multiplay เป็น On แล้วทดสอบ

## ฟังก์ชันอีเวนต์

สามารถใช้ฟังก์ชันอีเวนต์ได้ตั้งแต่ ZEPETO Mannequin เวอร์ชัน 1.1.0 เป็นต้นไป

### ZEPETO.Mannequin.Mannequin

| **การทำงาน**                                                                               | **รายละเอียด**                                                                                                                          |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| public static OnSelectedItem: UnityEngine\_Events.UnityEvent$2\<ItemContent, boolean>;     | จะถูกเรียกเมื่อเลือกไอเทมที่กำหนดในหน้าต่างซื้อคอสตูมบนหุ่น ItemContent ของไอเทมที่เลือกและสถานะการเลือกไอเทมจะเปลี่ยนไปเป็นค่า boolean |
| public static OnSucceededPurchaseItems: UnityEngine\_Events.UnityEvent$1\<ItemContent\[]>; | จะถูกเรียกเมื่อซื้อคอสตูมสำเร็จ ลิสต์ ItemContent ของข้อมูลไอเทมที่ซื้อจะถูกย้าย                                                        |
| public static OnFailedPurchaseItems: UnityEngine\_Events.UnityEvent$1\<ItemContent\[]>;    | จะถูกเรียกเมื่อซื้อคอสตูมไม่สำเร็จ ลิสต์ ItemContent ของข้อมูลไอเทมที่ซื้อไม่สำเร็จจะถูกย้าย                                            |
| public static OnAppliedItems: UnityEngine\_Events.UnityEvent$1\<ItemContent\[]>;           | จะถูกเรียกใช้เมื่อเลือกว่าจะใส่ไอเทมที่ซื้อหลังจากซื้อคอสตูมสำเร็จ ลิสต์ ItemContent ของข้อมูลไอเทมที่สวมใส่จะถูกย้าย                   |
| public static OnOpenedShopUI: UnityEngine\_Events.UnityEvent$1\<ItemContent\[]>;           | จะถูกเรียกเมื่อเปิดหน้าต่างซื้อคอสตูมบนหุ่น ลิสต์ ItemContent ของข้อมูลไอเทมในหน้าต่างซื้อจะถูกย้าย                                     |
| public static OnClosedShopUI: UnityEngine\_Events.UnityEvent;                              | จะถูกเรียกเมื่อหน้าต่างซื้อคอสตูมบนหุ่นปิด                                                                                              |

### ZEPETO.Mannequin.BasePreviewer

| **การทำงาน**                                                                                           | **รายละเอียด**                                                                                          |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| public OnChanged: UnityEngine\_Events.UnityEvent$1\<ZEPETO\_Mannequin\_BasePreviewer.ChangedValue\[]>; | จะถูกเรียกเสมอ หากข้อมูลไอเทมที่กำลังสวมใส่เปลี่ยนไปจากการกดที่หุ่น รวมถึงลิสต์ OnChagedValue จะถูกย้าย |

ข้อมูลตัวแปร Class Member มีดังต่อไปนี้

| public property : ZepetoPropertyFlag | ข้อมูลส่วนประกอบของคอสตูม |
| ------------------------------------ | ------------------------- |
| public id : string                   | item ID                   |


Mannequin.WorldCamera
---------------------

หากไม่ตั้งค่า Mannequin.WorldCamera ไว้ ไอคอน mannequin interaction และกล้องที่เชื่อมอยู่จะถูกจับเป็นกล้องที่มี Depth ตํ่า

หากต้องการตั้งค่ากล้องด้วยตนเอง สามารถใช้ Mannequin.WorldCamera ได้

| **Variable**          | **รายละเอียด**                                                                 |
| --------------------- | ------------------------------------------------------------------------------ |
| Mannequin.WorldCamera | ตัวแปรที่ช่วยให้สามารถตั้งค่ากล้องของไอคอน mannequin interaction ด้วยตัวเองได้ |

ตัวอย่างการตั้งค่า Mannequin.WorldCamera ใน MannequinScript เดิม

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { ZepetoPlayers, SpawnInfo } from 'ZEPETO.Character.Controller';
import { WorldService } from 'ZEPETO.World';
import { ItemContentsRequest, Mannequin, MannequinComponent, MannequinInteractable, MannequinPreviewer } from 'ZEPETO.Mannequin';
import { Object } from 'UnityEngine';
 
export default class MannequinScript extends ZepetoScriptBehaviour {
     
    private _previewer: MannequinPreviewer;
 
    Start() {
        // Code that creates a ZEPETO character based on the logged in ID 
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // Add Mannequin Interactable Component
            character.gameObject.AddComponent<MannequinInteractable>();
             
            // Code to set up the Mannequin Icon World Canvas Camera as the ZEPETO Camera
            Mannequin.WorldCamera = ZepetoPlayers.instance.LocalPlayer.zepetoCamera.camera;
        });
 
        // Find all Mannequin components
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();
 
        mannequins.forEach(m => {
            // Enter the Collider
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });
 
            // Exit the Collider
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```

## OnContentsLoaded API

A callback has been added that is triggered when all clothing items are fully loaded while using the Mannequin API to equip clothing items.

```typescript
public OnContentsLoaded: UnityEngine_Events.UnityEvent$1<ZEPETO_Mannequin_BasePreviewer.ChangedValue[]>;
```

An example of setting OnContentsLoaded in the existing MannequinScript is as follows.

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { ZepetoPlayers, SpawnInfo } from 'ZEPETO.Character.Controller';
import { WorldService } from 'ZEPETO.World';
import { ItemContentsRequest, Mannequin, MannequinComponent, MannequinInteractable, MannequinPreviewer } from 'ZEPETO.Mannequin';
import { Object } from 'UnityEngine';
 
export default class MannequinScript extends ZepetoScriptBehaviour {
 
    private _previewer: MannequinPreviewer;
 
    Start() {
        // Code that creates a ZEPETO character based on the logged in ID 
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // Add Mannequin Interactable Component
            character.gameObject.AddComponent<MannequinInteractable>();
        });
 
        // Find all Mannequin components
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();
 
        mannequins.forEach(m => {
            // Enter the Collider
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
 
                // Adding listener to OnContentsLoaded event
                this._previewer.OnContentsLoaded.AddListener((changedValues)=>{
                    console.log('Contents loaded:', changedValues);
                });
 
                this._previewer.PreviewContents();
            });
 
            // Exit the Collider
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    } 
}
```

**
