---
title: ZEPETO 模型
slug: world-sdk-guide-ch/Ob-A-zepeto
docTags: 
createdAt: 2024-09-02T08:34:13.134Z
---

ZEPETO 人体模型是一个支持试穿/销售服装的模型 API 包。

- 您将能够在世界中销售您制作的服装。
- 您可以创建一个穿着指定服装的 ZEPETO 角色 NPC 的模型。
- 您可以通过与模型或物体互动来购买服装。

::embed[[https://www.youtube.com/watch?v=sag4H7B5oM4](https://www.youtube.com/watch?v=sag4H7B5oM4)]{url="https://www.youtube.com/watch?v=sag4H7B5oM4"}



## 步骤 1 : 安装

窗口 → 包管理器 → 首先安装 ZEPETO.World 包 1.21.15 或更高版本。

之后，请安装 ZEPETO.Module 1.0.8 包。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-TSgCtsqcdWapxQM06E-W2-20240904-102421.png)

:::hint{type="danger"}
**❗️ 注意**

- 作为性能改进的一部分，独立包已集成到 ZEPETO.Module 1.0.8 及更高版本包中。
  - 请删除任何之前安装的 ZEPETO.Mannequin 包。
  - 在删除现有包后，请安装 ZEPETO.Module 包版本 1.0.8 或更高版本。
:::



## 步骤 2 : 检查要设置在模特上的物品 ID

:::hint{type="success"}
👍 **如何验证物品 ID**

- 您可以检查在 ZEPETO Studio 中点击物品产品时弹出的 URL。



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



- 复制物品 ID 作为随机数字，粘贴到 Unity 编辑器中，并在前面粘贴 'CR\_'。



  ![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-rVU--Xvoc6oq7-8Ie78Zz-20240904-102421.png)
:::

:::hint{type="info"}
**📘&#x20;**&#x5982;果您是第一次制作物品，请参考以下指南。\[[创建您的物品](docId\:cQP0hZXKF0dDpQFy6eeY8)]
:::



## 步骤 3 : 设置人形模型

人形模型可以通过三种方式制作。

:::hint{type="danger"}
- 只有您制作并发布的物品才能在 ZEPETO 人形模型中设置和出售。
- 在审核中被拒绝的物品无法出售。
- 因此，您必须登录 Unity 才能使用此功能。
- 当您发布带有人形功能的包时，您还必须以编辑身份登录。
- 使用 ZEPETO 模板和 ZEPETO 模型类型的人形模型时，消耗的资源等同于进入的 ZEPETO 角色数量，这可能会影响优化。
  - 如果您需要在世界中放置许多 ZEPETO 角色形状的人形模型，建议选择简单类型的人形模型，然后使用 [ZEPETO 基础模型](https://studio.zepeto.me/console/download/652892d3a80a9f5505ed888f) 作为对象。
:::



### 步骤 3-1 : 简单类型

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N--ZB5MK1nTCo6BIvGniTxd-20240904-102421.png" size="58" width="600" height="600" position="center" caption="使用简单类型人形的示例" showCaption="true"}



这是最基本的。可以将特定对象作为人形子对象添加以设置外观。

1\) 添加一个对象以进行交互。

2\) 必须添加碰撞体以与对象进行交互。

- 必须勾选 isTrigger。如果未勾选，则会自动处理为识别为触发器。
- 当您尝试添加人形组件而未添加碰撞体时，将出现如下所示的警告窗口。

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-QM-tzQ0BnaRbNa8J9T4No-20240904-102421.png" size="48" width="524" height="582" position="center" showCaption="false"}



3\) 将模特组件添加到对象中。

4\) 请在检查器中设置以下内容。

- 图标：默认显示衣架图标。您可以将其更改为自定义图标。
- 图标位置：如果不存在，图标默认出现在对象位置与模特组件一起。
  - 在添加位置对象后输入值，以使其出现在所需位置。
- ids：请填写您想要出售的服装项目的项目ID。

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-M1jGydsLXtPbR1zJCbnom-20240904-102421.png" size="82" width="490" height="635" position="center" showCaption="false"}



5\) 添加一个空对象，并将对象名称写为 MannequinScript。

6\) 添加 ZEPETO > Typescript，并将脚本名称写为 MannequinScript。

7\) 按如下填写。

```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() {
        // 代码创建基于登录 ID 的 ZEPETO 角色  
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
      
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // 添加 Mannequin 可交互组件
            character.gameObject.AddComponent<MannequinInteractable>();
        });

        // 查找所有 Mannequin 组件
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();

        mannequins.forEach(m => {
            // 进入碰撞体
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });

            // 退出碰撞体
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```

8\) 将脚本添加到对象后，按 \[▶︎(播放)] 按钮运行它。

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-ZB1lyvRY6z-3wb14cMp8Z-20240904-102421.png" size="84" width="846" height="612" position="center" showCaption="false"}



### 步骤 3-2 : ZEPETO 模型类型

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-y2l6LqHS0sK1TBWfzqujO-20240904-102422.png" size="36" width="503" height="740" position="center" caption="使用 ZEPETO 模型类型 Mannequin 的示例" showCaption="true"}



您可以通过将物品 ID 添加到 ZEPETO 角色 NPC 来设置人形的外观。

1\) 创建一个空对象并将其命名为人形。

2\) 必须添加碰撞体以便与对象进行交互。

- 必须勾选 isTrigger。如果未勾选，则会自动处理为识别为触发器。

3\) 将人形组件添加到对象。

4\) 请在检查器中设置以下内容。

- 图标：默认显示衣架图标。您可以将其更改为自定义图标。
- 图标位置：如果不存在，图标默认出现在带有人形组件的对象位置。
  - 在添加位置对象后输入值，以使其出现在所需位置。
- ids：请填写您想要出售的服装物品的 ID。
- 姿势：您可以选择一个姿势并进行设置。
  - 未来，我们将以姿势 ID 的形式提供您想要的姿势。



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



:::hint{type="success"}
**👍&#x20;**&#x5982;果您已经在场景中有一个人形脚本，您可以跳过后面的步骤。
:::

5\) 添加一个空对象，并将对象名称写为 MannequinScript。

6\) 添加 ZEPETO > Typescript，并将脚本名称写为 MannequinScript。

7\) 按如下填写。

```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() {
        // 代码创建基于登录 ID 的 ZEPETO 角色  
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
      
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // 添加人形可交互组件
            character.gameObject.AddComponent<MannequinInteractable>();
        });

        // 查找所有人形组件
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();

        mannequins.forEach(m => {
            // 进入碰撞体
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });

            // 退出碰撞体
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```

8\) 添加脚本到对象后，按下 \[▶︎(播放)] 按钮以运行它。

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



### 步骤 3-3 : ZEPETO 模板类型

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-yrpKiZQT7yT_4S_h7olEe-20240904-102422.png" size="48" width="503" height="740" position="center" caption="使用 ZEPETO 模板类型人形的示例" showCaption="true"}



如果您输入一个 ZEPETO id，人物所穿的衣服将作为人形出现。然而，如果这不是您制作的衣服，则不会显示这些衣服。

1\) 创建一个空对象并将其命名为人形。

2\) 必须添加碰撞体以与对象进行交互。

- 必须勾选 isTrigger。如果未勾选，则会自动处理为识别为触发器。

3\) 将人形组件添加到对象中。

4\) 请在检查器中设置以下内容。

- 图标：默认显示衣架图标。您可以将其更改为自定义图标。
- 图标位置：如果不存在，图标默认出现在带有人形组件的对象位置。
  - 输入值后添加位置对象，以使其出现在所需位置。
- zepeto id：请输入 ZEPETO id。
- 姿势：您可以选择一个姿势并进行设置。
  - 未来，我们将以姿势 id 的形式提供您想要的姿势。

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



:::hint{type="success"}
**👍&#x20;**&#x5982;果您在场景中已经有一个人形脚本，您可以跳过后面的步骤。
:::

5\) 添加一个空对象，并将对象名称写为 MannequinScript。

6\) 添加 ZEPETO > Typescript，并将脚本名称写为 MannequinScript。

7\) 按如下填写。

```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() {
        // 代码根据登录 ID 创建 ZEPETO 角色  
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);

        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // 添加 Mannequin 可交互组件
            character.gameObject.AddComponent<MannequinInteractable>();
        });

        // 查找所有 Mannequin 组件
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();

        mannequins.forEach(m => {
            // 进入碰撞体
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });

            // 退出碰撞体
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```

8\) 将脚本添加到对象后，按 \[▶︎(播放)] 按钮运行它。

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



## 步骤 4 : 使用 Mannequin 购买

如果您正确完成了人形设置，当您进入人形对象的碰撞体区域时，将出现一个交互图标。

当您点击图标时，购买窗口将出现，并显示您在模特上设置的物品。

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-eRDdgTbmEL8FRMK7dLhua-20240904-102422.png" size="80" width="1092" height="803" position="center" showCaption="false"}

您可以通过点击物品来试穿。

- 您已经拥有的物品将用勾号标记，而不是价格。
  当您离开模特物体的碰撞区域时，试穿将被取消，您将返回到原来的服装。

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

以下是根据测试场景购买过程的工作方式。



## 步骤 5 : 模特多人同步

多人游戏需要使用模特功能同步其他玩家穿着的服装。

:::hint{type="success"}
**👍&#x20;**&#x57FA;本的多人游戏设置应在处理以下内容之前完成。
:::

1\) 添加客户端脚本。 添加 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) => {
            // 当你点击打开商店按钮时
            this.OpenedShopUI(item)
        });
        Mannequin.OnClosedShopUI.AddListener(() => {
            // 当你点击关闭商店按钮时
            this.ClosedShopUI()
        });
        Mannequin.OnSelectedItem.AddListener((itemcontent: ItemContent, select: boolean) => {
            // 选择物品时的动作
        });
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const myPlayer = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer;
 
            // 人体模特
            const character = myPlayer.character;
            character.gameObject.AddComponent<MannequinInteractable>();
 
            console.log("本地 _context 设置");
            this._context = character.Context;
 
            const mannequins = Object.FindObjectsOfType<MannequinComponent>()
            mannequins.forEach(mannequin => {
                // 当你进入人体模特碰撞体时
                mannequin.onActive.AddListener(contents => {
                    if (this._currentMannequinComponent != null && this._currentMannequinComponent == mannequin) {
                        return;
                    }
 
                    if (contents == null || contents.length == 0) {
                        console.log("没有人体模特数据");
                        return;
                    }
 
                    if (this._isOpenMannequinUI) {
                        this.BreakMannequin();
                    }
 
                    this._selectMannequinComponent = mannequin;
                    Mannequin.OpenUI(contents);
                    console.log("onActive");
                });
 
                // [选项] 当你离开人体模特碰撞体时
                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() {
        // [选项] 同步每个玩家的衣服
        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;
    }
 
}
```

3\) 创建层次结构 > 空对象并将其命名为人形控制器。

4\) 将您刚刚编写的脚本添加到人偶控制器中。

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-8MSxbxfTuoV4rKAo-g4_k-20240904-102422.png" size="96" width="452" height="264" position="center" showCaption="false"}



5\) 接下来，参考以下内容编写服务器代码，位于 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) {
        // 当房间对象被创建时调用。
        // 处理房间对象的状态或数据初始化。
        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; // 角色控制器 V2
        });
 
        // 人偶服务器代码
        this.ChangedItems = new Map<string, Map<string, string>>();
 
        this.onMessage<CharacterItem[]>(this.MESSAGE_TYPE.OnChangedItem, (client, message) => {
            // 覆盖衣物并设置新部件
            if (this.ChangedItems.has(client.userId)) {
                const changedItemMap = this.ChangedItems.get(client.userId);
                for (const characterItem of message) {
                    if (characterItem.property == Cloth.DRESS) {
                        // 在裙子（22）的情况下，移除上衣（19）和下装（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) {
                        // 如果是上衣（19）或下装（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 {
                // 初始注册
                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) {
 
        // 创建在 schemas.json 中定义的玩家对象并设置初始值。
        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;
        }
 
        // 使用 sessionId 管理玩家对象，这是客户端对象的唯一键值。
        // 客户端可以通过将 add_OnAdd 事件添加到 players 对象来检查有关玩家对象的信息。
        this.state.players.set(client.sessionId, player);
    }
 
    async onLeave(client: SandboxPlayer, consented?: boolean) {
 
        // 通过设置 allowReconnection，可以保持电路的连接，但在基本指南中立即清理。
        // 客户端可以通过将 add_OnRemove 事件添加到 players 对象来检查有关已删除玩家对象的信息。
        this.state.players.delete(client.sessionId);
        if (this.ChangedItems.has(client.sessionId)) {
            this.ChangedItems.delete(client.sessionId);
        }
    }
}
```

6\) 打开多人服务器并进行测试。



## 事件功能

事件功能可从 ZEPETO Mannequin 1.1.0 开始使用。



### ZEPETO.模特.模特

| **功能**                                                                                     | **描述**                                           |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------ |
| public static OnSelectedItem: UnityEngine\_Events.UnityEvent$2\<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.模特.基础预览器

| **功能**                                                                                                 | **描述**                                          |
| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| public OnChanged: UnityEngine\_Events.UnityEvent$1\<ZEPETO\_Mannequin\_BasePreviewer.ChangedValue\[]>; | 每当通过按压模型更改装备物品信息时，都会调用此方法，并传递 OnChagedValue 列表。 |

OnChagedValue 类的成员变量信息如下：

| public property : ZepetoPropertyFlag | 服装部件信息 |
| ------------------------------------ | ------ |
| public id : string                   | 物品 ID  |



## 人偶.世界相机

如果未设置人偶.世界相机，则人偶交互图标的相机将设置为低深度的相机。

如果您希望设置特定的相机，可以使用人偶.世界相机来指定一个。

| **变量**  | **描述**                |
| ------- | --------------------- |
| 人偶.世界相机 | 一个允许您手动设置人偶交互图标相机的变量。 |



以下是现有MannequinScript中Mannequin.WorldCamera的示例设置。

```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() {
        // 代码根据登录的ID创建ZEPETO角色 
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // 添加Mannequin可交互组件
            character.gameObject.AddComponent<MannequinInteractable>();
             
            // 代码将Mannequin图标世界画布相机设置为ZEPETO相机
            Mannequin.WorldCamera = ZepetoPlayers.instance.LocalPlayer.zepetoCamera.camera;
        });
 
        // 查找所有Mannequin组件
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();
 
        mannequins.forEach(m => {
            // 进入碰撞体
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
                this._previewer.PreviewContents();
            });
 
            // 退出碰撞体
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    }
}
```





## OnContentsLoaded API

当使用 Mannequin API 装备服装时，添加了一个回调，当所有服装项目完全加载时触发。

:::CodeblockTabs
API

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



在现有的 MannequinScript 中设置 OnContentsLoaded 的示例如下。

:::CodeblockTabs
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() {
        // 根据登录 ID 创建 ZEPETO 角色的代码 
        // ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, SpawnInfo.Default, true);
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            const character = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            // 添加 Mannequin Interactable 组件
            character.gameObject.AddComponent<MannequinInteractable>();
        });
 
        // 查找所有 Mannequin 组件
        const mannequins = Object.FindObjectsOfType<MannequinComponent>();
 
        mannequins.forEach(m => {
            // 进入碰撞体
            m.onActive.AddListener(contents => {
                Mannequin.OpenUI(contents);
                const zepetoContext = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.Context;
                this._previewer = new MannequinPreviewer(zepetoContext, contents);
 
                // 添加 OnContentsLoaded 事件的监听器
                this._previewer.OnContentsLoaded.AddListener((changedValues)=>{
                    console.log('内容已加载:', changedValues);
                });
 
                this._previewer.PreviewContents();
            });
 
            // 退出碰撞体
            m.onCancel.AddListener(() => {
                Mannequin.CloseUI();
                this._previewer?.ResetContents();
            });
        });
    } 
}
```
:::

