---
title: 产品 API 使用示例
slug: world-sdk-guide-ch/r9OB-api
docTags: 
createdAt: 2024-09-02T08:34:13.134Z
---

以下是使用产品API管理世界货币和世界产品的示例。

此示例使您能够基本了解产品API，并实现服务器与客户端之间的消息发送和接收，以便您可以轻松测试授予和扣除世界货币和产品。

此示例包括世界货币和世界产品客户端脚本的描述，随后是处理所有服务器端处理的集成服务器脚本，并包括以下功能：

- 授予世界货币
- 扣除世界货币
- 在不扣除货币的情况下授予世界产品
- 扣除世界产品

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-LVomjGT16SVteQlTOo7ug-20240904-102419.gif)



## 授予与扣除世界货币

以下是管理世界货币授予和扣除的完整客户端代码，以及如何实现它。

### 货币客户端脚本

:::CodeblockTabs
CurrencyManagerSample

```typescript
import { Button, Text } from 'UnityEngine.UI';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { LocalPlayer, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room, RoomData } from 'ZEPETO.Multiplay';
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
 
export default class CurrencyManagerSample extends ZepetoScriptBehaviour {
 
    public currencyId: string;
    public increaseQuantity: number;
    public decreaseQuantity: number;
    public increaseCurrencyBtn: Button;
    public decreaseCurrencyBtn: Button;
    public balanceText: Text;
    public multiplay: ZepetoWorldMultiplay;
 
    private _localPlayer: LocalPlayer;
 
    Start() {
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._localPlayer = ZepetoPlayers.instance.LocalPlayer;
            this.loadCurrencyBalance(this.currencyId);
        });
 
        // 处理房间加入事件以更新货币 UI
        this.multiplay.RoomJoined += (room: Room) => {
            room.AddMessageHandler("SyncCurrencyInfo", (message: CurrencyMessage) => {
                const currentCurrencyId = message.currencyId;
                const currentQuantity = message.quantity;
                this.balanceUIUpdate(currentCurrencyId, currentQuantity);
            });
        };
 
        // 增加货币的按钮事件监听器：请求信用
        this.increaseCurrencyBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("currencyId", this.currencyId);
            data.Add("quantity", this.increaseQuantity);
            this.multiplay.Room?.Send("CreditCurrency", data.GetObject());
        })
 
        // 减少货币的按钮事件监听器：请求借记
        this.decreaseCurrencyBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("currencyId", this.currencyId);
            data.Add("quantity", this.decreaseQuantity);
            this.multiplay.Room?.Send("DebitCurrency", data.GetObject());
        })
 
    }
 
    // 请求指定货币的当前数量
    private loadCurrencyBalance(currentCurrencyId: string) {
        this.multiplay.Room?.Send("LoadBalance", currentCurrencyId);
    }
 
    // 更新 UI 以反映当前货币的数量
    private balanceUIUpdate(currentCurrencyId: string, currentQuantity: number) {
        this.balanceText.text = currentQuantity.toString();
    }
 
}
 
// 货币同步消息的接口
interface CurrencyMessage {
    currencyId: string,
    quantity: number,
}
```
:::



### 货币客户端脚本描述

- 当角色加载时，会向服务器发送房间消息以加载现有的货币余额。然后根据从服务器接收到的余额信息更新 UI。

```typescript
this.multiplay.Room?.Send("LoadBalance", currentCurrencyId);
```

- 为了促进消息交换，`CurrencyMessage` 接口被定义。

```typescript
interface CurrencyMessage {
    currencyId: string,
    quantity: number,
}
```

- 当用户增加或减少他们的货币时，他们会作为房间消息向服务器发送请求。
  它包括有关要增加或减少哪种货币以及增加或减少多少的数据。

```typescript
this.multiplay.Room?.Send("CreditCurrency", data.GetObject());
this.multiplay.Room?.Send("DebitCurrency", data.GetObject());
```

- 服务器然后处理货币的增加或减少，并将最终余额信息发送给客户端。客户端接收此信息并更新用户界面。

[查看完整的服务器脚本](docId\:r9OBhkU_jUrf6kHVCqJHa)&#x20;


```typescript
this.multiplay.RoomJoined += (room: Room) => {
    room.AddMessageHandler("SyncCurrencyInfo", (message: CurrencyMessage) => {
        const currentCurrencyId = message.currencyId;
        const currentQuantity = message.quantity;
        this.updateBalanceUI(currentCurrencyId, currentQuantity);
    });
};
```



## 授予与扣除世界产品

以下是管理世界产品授予和扣除的完整客户端代码，以及如何实现它。

### 产品客户端脚本

:::CodeblockTabs
ProductManagerSample

```typescript
import { Button, Text } from 'UnityEngine.UI';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { LocalPlayer, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room, RoomData } from 'ZEPETO.Multiplay';
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
import { InventoryRecord, InventoryService } from 'ZEPETO.Inventory';
import { WaitUntil } from 'UnityEngine';
 
export default class ProductManagerSample extends ZepetoScriptBehaviour {
 
    public productId: string;
    public productAddQuantity: number;
    public acquireItemBtn: Button;
    public useItemBtn: Button;
    public itemCountText: Text;
    public multiplay: ZepetoWorldMultiplay;
 
    private _localPlayer: LocalPlayer;
 
    Start() {
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._localPlayer = ZepetoPlayers.instance.LocalPlayer;
            this.StartCoroutine(this.refreshProductUI());
        });
 
        // 处理房间加入事件以更新产品 UI
        this.multiplay.RoomJoined += (room: Room) => {
            room.AddMessageHandler("SyncProductInfo", (message: ProductMessage) => {
                this.StartCoroutine(this.refreshProductUI());
            });
        };
 
        // 获取物品的按钮事件监听器：添加产品
        this.acquireItemBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("productId", this.productId);
            data.Add("quantity", this.productAddQuantity);
            this.multiplay.Room?.Send("AddProduct", data.GetObject());
        })
 
        // 使用物品的按钮事件监听器：使用产品
        this.useItemBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("productId", this.productId);
            data.Add("quantity", 1);
            this.multiplay.Room?.Send("UseProduct", data.GetObject());
        })
 
    }
     
    private * refreshProductUI() {
        const request = InventoryService.GetAsync(this.productId);
        yield new WaitUntil(() => request.keepWaiting == false);
        // 如果请求成功，更新产品 UI
        if (request.responseData.isSuccess) {
            this.updateItemCountText(request.responseData.product);
        }
        else {
            console.log("加载产品物品失败。");
        }
    }
 
    private updateItemCountText(item: InventoryRecord) {
        if (item != null) {
            this.itemCountText.text = item.quantity.toString();
        }
        else {
            this.itemCountText.text = "0";
        }
    }
 
}
 
// 产品消息的接口。
interface ProductMessage {
    productId: string,
    productAction: ProductAction,
}
 
// 定义产品操作类型的枚举。
export enum ProductAction {
    Use,
    Add,
}
```
:::



### 产品客户端脚本描述

- 当角色加载时，使用 `InventoryService` 从 [ZEPETO.Inventory](https://developer.zepeto.me/docs/product/namespaces/ZEPETO_Inventory/) 来加载初始产品库存。然后更新UI。

```typescript
private * refreshProductUI() {
        const request = InventoryService.GetAsync(this.productId);
        yield new WaitUntil(() => request.keepWaiting == false);
        if (request.responseData.isSuccess) {
            this.updateItemCountText(request.responseData.product);
        }
        else {
            console.log("加载产品项失败。");
        }
    }
```

- 要发送和接收消息，定义 `ProductMessage` 接口。定义一个 `ProductAction` 枚举以供示例实现。

```typescript
interface ProductMessage {
    productId: string,
    productAction: ProductAction,
}
 
export enum ProductAction {
    使用,
    添加,
}
```

- 当您增加或减少产品时，您将该数据作为房间消息发送到服务器。
  它包括有关要增加或减少哪个产品以及增加或减少多少的数据。

```typescript
this.multiplay.Room?.Send("AddProduct", data.GetObject());
this.multiplay.Room?.Send("UseProduct", data.GetObject());
```

- 处理产品变更后，服务器将最终库存信息发送给客户端，客户端相应地更新UI。

[查看完整服务器脚本](docId\:r9OBhkU_jUrf6kHVCqJHa)&#x20;

```typescript
this.multiplay.RoomJoined += (room: Room) => {
    room.AddMessageHandler("SyncProductInfo", (message: ProductMessage) => {
        this.StartCoroutine(this.refreshProductUI());
    });
};
```

:::hint{type="success"}
- 通常，世界产品需要花费货币进行购买。
- 在这种情况下，您可以轻松地通过一个**产品购买按钮**进行销售，参考指南[将你的世界货币化！设置产品和货币](docId\:Uw6k8dr1wHl1WgQBlq64g)&#x20;
:::



## 服务器脚本

以下是管理世界货币和产品的完整服务器代码，以及如何实现它。

### 完整服务器脚本

```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { loadCurrency } from "ZEPETO.Multiplay.Currency";
import { loadInventory } from "ZEPETO.Multiplay.Inventory";

export default class extends Sandbox {

    async onCreate(options: SandboxOptions) {

        // 货币
        //请求加载货币
        this.onMessage("LoadBalance", (client, message: string) => {
            this.loadBalance(client, message);
        });
        //请求添加信用
        this.onMessage("CreditCurrency", (client, message: CurrencyMessage ) => {
            const currencyId = message.currencyId;
            const quantity = message.quantity;
            this.addCredit(client, currencyId, quantity);
        });
        //请求扣除信用
        this.onMessage("DebitCurrency", (client, message: CurrencyMessage ) => {
            const currencyId = message.currencyId;
            const quantity = message.quantity;
            this.onDebit(client, currencyId, quantity);
        });

        // 产品
        //请求添加物品
        this.onMessage("AddProduct", (client, message: any) => {
            const productId = message.productId;
            const quantity = message.quantity;
            this.addProduct(client, productId, quantity);
        });
        //请求使用物品
        this.onMessage("UseProduct", (client, message: any) => {
            const productId = message.productId;
            const quantity = message.quantity;
            this.useProduct(client, productId, quantity);
        });

    }

    async addCredit(client: SandboxPlayer, currencyId: string, quantity: number) {
        try {
            const currency = await loadCurrency(client.userId);
            await currency.credit(currencyId, quantity);
            this.loadBalance(client, currencyId);
        } catch (e) {
            console.error(`AddCredit 错误: ${e}`);
        }
    }

    async onDebit(client: SandboxPlayer, currencyId: string, quantity: number) {
        try {
            const currency = await loadCurrency(client.userId);
            if (await currency.debit(currencyId, quantity) === true) {
                this.loadBalance(client, currencyId);
            } else {
                console.error("DebitCredit 错误: 货币不足");
            }
        } catch (e) {
            console.error(`DebitCredit 错误: ${e}`);
        }
    }

    async loadBalance(client: SandboxPlayer, currencyId: string) {
        try {
            const currency = await loadCurrency(client.userId);
            let balancesObject = await currency.getBalances();
            let balancesMap: Map<string, number> = new Map(Object.entries(balancesObject));
            const specificCurrencyID = currencyId;
            const specificBalance = balancesMap.get(specificCurrencyID) ?? 0;
            const currencySync: CurrencyMessage = {
                currencyId: specificCurrencyID,
                quantity: specificBalance
            }
            client.send("SyncCurrencyInfo", currencySync);
        } catch (e) {
            console.error(`loadBalance 错误: ${e}`);
        }
    }

    async addProduct(client: SandboxPlayer, productId: string, quantity: number) {
        try {
            const inventory = await loadInventory(client.userId);
            await inventory.add(productId, quantity);
            const productMessage: ProductMessage = {
                productId: productId,
                productAction: ProductAction.Add
            }
            client.send("SyncProductInfo", productMessage);
        } catch (e) {
            console.error(`addProduct 错误: ${e}`);
        }
    }

    async useProduct(client: SandboxPlayer, productId: string, quantity: number) {
        try {
            const inventory = await loadInventory(client.userId);
            await inventory.use(productId, quantity);
            const productMessage: ProductMessage = {
                productId: productId,
                productAction: ProductAction.Use
            }
            client.send("SyncProductInfo", productMessage);
        } catch (e) {
            console.error(`useProduct 错误: ${e}`);
        }
    }

    onJoin(client: SandboxPlayer) {
        // 加入逻辑在这里
    }

    onTick(deltaTime: number): void {
        // Tick 逻辑在这里
    }

    onLeave(client: SandboxPlayer, consented?: boolean) {
        // 处理离开逻辑在这里
    }

}

// 货币消息接口
interface CurrencyMessage {
    currencyId: string,
    quantity: number,
}

// 产品消息接口。
interface ProductMessage {
    productId: string,
    productAction: ProductAction,
}

// 定义产品操作类型的枚举。
export enum ProductAction {
    Use,
    Add,
}
```



### 服务器脚本描述

- 通过管理世界货币 [ZEPETO.Multiplay.Currency](https://developer.zepeto.me/docs/multiplay-server/namespaces/ZEPETO_Multiplay_Currency/).
- 使用 `currency.credit()` 和 `currency.debit()` 来增加或减少所需货币的余额。之后，调用 `currency.getBalances()` 来获取每种货币的当前余额。

```typescript
currency.credit(currencyId, quantity);
currency.debit(currencyId, quantity);
currency.getBalances();
```

- 通过管理世界产品 [ZEPETO.Multiplay.Inventory](https://developer.zepeto.me/docs/multiplay-server/namespaces/ZEPETO_Multiplay_Inventory).
- 使用 `inventory.add()` 和 `inventory.use()` 来增加或减少所需货币的余额。

```typescript
inventory.add(productId, quantity);
inventory.use(productId, quantity);
```

- 处理来自客户的增加或减少货币和产品的请求。
- 使用`loadBalance()`来获取在ZEPETO Studio中注册的货币信息。
- 如果有多种货币，您可以根据特定的货币ID值进行条件筛选，以仅获取该货币的余额。
- 将最终余额值作为房间消息传递给客户端。

