玩家物品栏

要求

若要使用玩家物品栏,必须为游戏定义目录。 阅读我们的目录教程以了解详细信息。

注意

还可以选择为目录定义商店。

目录是游戏中可用的所有物品的列表,而商店是目录中具有唯一定价选项的物品子集。

可以对每个目录定义多个商店,以便可以基于用户细分或其他因素,向玩家展示不同的物品集。

通过 Game Manager,或通过我们的管理员 SetCatalogItemsUpdateCatalogItems API 调用定义目录后,将能够在客户端和服务器上使用各种物品栏 API 调用。

API 概述

所有物品栏 API 调用都设计为是服务器权威且安全的。 如果正确使用,客户将无法作弊或获取并未赢取的物品。

客户端

服务器

下面显示的示例说明调用这些 API 方法的代码块,并为玩家物品栏设置基本用例。

注意

为提供参考,这些示例来自 Unicorn Battle,这是一款作为演示 PlayFab 功能的示例而构建的游戏。

下面使用的 AU 虚拟货币是 Gold,这是通过与怪物战斗而赢取的免费货币(请参阅我们的货币教程)。

在开始之前,我们将定义几个将在本指南大多数示例中使用和重用的实用函数。

// **** Shared example utility functions ****

// This is typically NOT how you handle success
// You will want to receive a specific result-type for your API, and utilize the result parameters
void LogSuccess(PlayFabResultCommon result) {
    var requestName = result.Request.GetType().Name;
    Debug.Log(requestName + " successful");
}

// Error handling can be very advanced, such as retry mechanisms, logging, or other options
// The simplest possible choice is just to log it
void LogFailure(PlayFabError error) {
    Debug.LogError(error.GenerateErrorReport());
}

仅限客户端的示例:购买和使用生命药水

客户端 API 调用顺序:PurchaseItemGetUserInventoryConsumeItem

首先必须从定义目录中的物品开始。

PlayFab - 经济 - 编辑目录物品

对于生命药水有一些 CatalogItem 要求。

  • PurchaseItem 需要正的物品价格 (5 AU)。
  • ConsumeItem 要求物品是 Consumable,并且物品计数为正 (3)。
  • 进行购买的玩家必须有 5 AU 的虚拟货币余额可用。

下面提供了每个调用的代码。

void MakePurchase() {
    PlayFabClientAPI.PurchaseItem(new PurchaseItemRequest {
        // In your game, this should just be a constant matching your primary catalog
        CatalogVersion = "CharacterClasses",
        ItemId = "MediumHealthPotion",
        Price = 5,
        VirtualCurrency = "AU"
    }, LogSuccess, LogFailure);
}

void GetInventory() {
    PlayFabClientAPI.GetUserInventory(new GetUserInventoryRequest(), LogSuccess, LogFailure);
}

void ConsumePotion() {
    PlayFabClientAPI.ConsumeItem(new ConsumeItemRequest {
        ConsumeCount = 1,
        // This is a hex-string value from the GetUserInventory result
        ItemInstanceId = "potionInstanceId"
    }, LogSuccess, LogFailure);
}

示例:玩家被授予并打开容器

API 调用顺序:

首先必须从目录中定义的容器开始。 对于此示例中的容器,我们选择了 CrystalContainer

此示例还介绍了如何使用钥匙(这是可选物品,也必须处于玩家物品栏中才能使 UnlockContainerInstance 调用成功)打开容器。

PlayFab - 经济 - 编辑目录容器

此示例中针对 CrystalContainerCatalogItem 要求包括:

  • CrystalContainer 定义为容器

  • 容器可以选择定义一个对容器解除锁定所需的钥匙物品(在此例中是 CrystalKey)。

  • 强烈建议容器和任何钥匙都应属于易耗品,并且使用计数为正,这样才能在使用后从玩家物品栏中将其移除。

服务器代码

void GrantItem() {
    PlayFabServerAPI.GrantItemsToUser(new GrantItemsToUserRequest {
        // In your game, this should just be a constant
        CatalogVersion = "CharacterClasses",
        // Servers must define which character they're modifying in every API call
        PlayFabId = "playFabId",
        ItemIds = new List<string> { "CrystalContainer" }
    }, LogSuccess, LogFailure);
}

客户端代码

void OpenContainer() {
    PlayFabClientAPI.UnlockContainerInstance(new UnlockContainerInstanceRequest {
        // In your game, this should just be a constant matching your primary catalog
        CatalogVersion = "CharacterClasses",
        ContainerItemInstanceId = "containerInstanceId",
        KeyItemInstanceId = "keyInstanceId"
    }, LogSuccess, LogFailure);
}

消耗钥匙和容器

在之前的示例中,已建议钥匙和/或容器应是易耗品,不过这只是建议。

但是如果容器及其钥匙(如果有)不是易耗品,则可以无限次重新打开容器,从而可以每次都向玩家授权使用其内容。

因为玩家物品栏容量不是无限的,非常不建议采用此模式。 在解除锁定易耗品容器时,该容器及使用的易耗品钥匙都会自动使其使用计数递减,从而在使用计数达到零时将它们从玩家物品栏中移除。

可行选项

易耗品容器,无钥匙:最基本的模式,其中容器会在打开时消耗,并且没有钥匙。

易耗品容器,易耗品钥匙:简单锁定容器情况,使玩家可以使用钥匙打开容器。 两者都是易耗品,玩家只能使用具有剩余使用次数的钥匙打开具有剩余使用次数的容器。

耐用品容器,易耗品钥匙:这使玩家可以在每次找到钥匙时打开容器。 钥匙是易耗品,仅当钥匙具有剩余使用次数时才能打开容器。

易耗品容器,耐用品钥匙:此项允许玩家-保留一个钥匙,它可以打开将它作为关键物品的所有容器。 容器是易耗品,但是玩家保留在以后使用钥匙打开容器的能力。

示例:从玩家购买物品栏物品

没有用于从玩家回购物品栏物品的内置 API,因为该过程是特定于游戏的。 但是,可以使用现有 API 方法创建自己的 SellItem 体验:

  • PlayFab 服务器 API RevokeInventoryItem** 允许你移除物品栏物品。

  • PlayFab 服务器 API AddUserVirtualCurrency** 可以退回相应金额的虚拟货币。 当前无法通过 PlayFab API 方法退回真实货币。

注意

物品和虚拟货币具有密切关系。 有关详细信息,请参阅我们的货币教程。

以下 CloudScript 函数将所述的两个服务器函数合并为客户端可访问的单个调用。

var SELL_PRICE_RATIO = 0.75;
function SellItem_internal(soldItemInstanceId, requestedVcType) {
    var inventory = server.GetUserInventory({ PlayFabId: currentPlayerId });
    var itemInstance = null;
    for (var i = 0; i < inventory.Inventory.length; i++) {
        if (inventory.Inventory[i].ItemInstanceId === soldItemInstanceId)
            itemInstance = inventory.Inventory[i];
    }
    if (!itemInstance)
        throw "Item instance not found"; // Protection against client providing incorrect data
    var catalog = server.GetCatalogItems({ CatalogVersion: itemInstance.CatalogVersion });
    var catalogItem = null;
    for (var c = 0; c < catalog.Catalog.length; c++) {
        if (itemInstance.ItemId === catalog.Catalog[c].ItemId)
            catalogItem = catalog.Catalog[c];
    }
    if (!catalogItem)
        throw "Catalog Item not found"; // Title catalog consistency check (You should never remove a catalog/catalogItem if any player owns that item
    var buyPrice = 0;
    if (catalogItem.VirtualCurrencyPrices.hasOwnProperty(requestedVcType))
        buyPrice = catalogItem.VirtualCurrencyPrices[requestedVcType];
    if (buyPrice <= 0)
        throw "Cannot redeem this item for: " + requestedVcType; // The client requested a virtual currency which doesn't apply to this item
    // Once we get here all safety checks are passed - Perform the sell
    var sellPrice = Math.floor(buyPrice * SELL_PRICE_RATIO);
    server.AddUserVirtualCurrency({ PlayFabId: currentPlayerId, Amount: sellPrice, VirtualCurrency: requestedVcType });
    server.RevokeInventoryItem({ PlayFabId: currentPlayerId, ItemInstanceId: soldItemInstanceId });
}

handlers.SellItem = function (args) {
    if (!args || !args.soldItemInstanceId || !args.requestedVcType)
        throw "Invalid input parameters, expected soldItemInstanceId and requestedVcType";
    SellItem_internal(args.soldItemInstanceId, args.requestedVcType);
};

最佳做法

  • 确保在进行任何更改之前验证所有客户端输入信息是否都有效

  • CloudScript 不是原子型,因此调用顺序十分重要:AddUserVirtualCurrency 可能会成功,而 RevokeInventoryItem 可能会失败。

提示

在此过程中向玩家给予他们赢取的某个物品,通常比在没有补偿的情况下取走某个物品更好。

随后可以从客户端访问此 CloudScript 函数。

void SellItem()
{
    PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest
    {
        // This must match "SellItem" from the "handlers.SellItem = ..." line in the CloudScript file
        FunctionName = "SellItem",
        FunctionParameter = new Dictionary<string, string>{
            // This is a hex-string value from the GetUserInventory result
            { "soldItemInstanceId", "sellItemInstanceId" },
            // Which redeemable virtual currency should be used in your game
            { "requestedVcType", "AU" },
        }
    }, LogSuccess, LogFailure);
}